package com.zyq.config;

import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.CacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisPassword;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettucePoolingClientConfiguration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.util.Assert;

import javax.annotation.PostConstruct;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.time.Duration;

/**
 * RedisConfig
 * @author xiaochi
 * @date 2021/12/11 22:13
 */
@Slf4j
@Configuration
public class RedisConfig {

    @Value("${spring.redis.database}")
    private Integer database;
    @Value("${spring.redis.host}")
    private String host;
    @Value("${spring.redis.port}")
    private Integer port;
    @Value("${spring.redis.password}")
    private String password;
    @Value("${spring.redis.timeout}")
    private Long timeout;
    @Value("${spring.redis.lettuce.pool.max-active}")
    private Integer maxActive;
    @Value("${spring.redis.lettuce.pool.max-wait}")
    private Long maxWait;
    @Value("${spring.redis.lettuce.pool.max-idle}")
    private Integer maxIdle;
    @Value("${spring.redis.lettuce.pool.min-idle}")
    private Integer minIdle;
    @Value("${spring.redis.expired}")
    private Long expired;

    @Autowired
    private Environment environment;

    @PostConstruct
    public void initConfig(){
        StringBuilder redisConfig = new StringBuilder(255);
        redisConfig.append("---- Redis配置 ,["+environment.getProperty("spring.profiles.active")+"] ----\r\n");
        redisConfig.append("  database：").append(database).append("\r\n");
        redisConfig.append("  host：").append(host).append("\r\n");
        redisConfig.append("  port：").append(port).append("\r\n");
        redisConfig.append("  password：").append(password).append("\r\n");
        redisConfig.append("  timeout：").append(timeout).append("\r\n");
        redisConfig.append("  maxActive：").append(maxActive).append("\r\n");
        redisConfig.append("  maxWait：").append(maxWait).append("\r\n");
        redisConfig.append("  maxIdle：").append(maxIdle).append("\r\n");
        redisConfig.append("  minIdle：").append(minIdle).append("\r\n");
        redisConfig.append("  expired：").append(expired);
        log.info(redisConfig.toString());
    }

    /*  反序列化时泛型自动转换
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory connectionFactory) {
        //初始化一个RedisCacheWriter
        RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(connectionFactory);
        Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer(Object.class);
        RedisSerializationContext.SerializationPair<Object> pair = RedisSerializationContext.SerializationPair.fromSerializer(serializer);
        RedisCacheConfiguration defaultCacheConfig=RedisCacheConfiguration.defaultCacheConfig().serializeValuesWith(pair);
        return new RedisCacheManager(redisCacheWriter, defaultCacheConfig);
    }*/

    /**
     * Redisson 配置
     * @return
     */
    /*@Bean(destroyMethod="shutdown")
    public RedissonClient redissonClient(){
        Config config = new Config();
        config.useSingleServer().setPassword(password)
                .setDatabase(database)
                *//*.setConnectionPoolSize(maxActive) // 连接池大小，默认64
                .setConnectionMinimumIdleSize(minIdle) // 最小空闲连接数，默认32
                .setRetryAttempts(3) // 命令失败重试次数 3
                .setRetryInterval(1500) // 命令重试发送时间间隔(毫秒) 默认1500
                .setTimeout(timeout.intValue()) // 命令等待超时(毫秒) 默认10000
                .setConnectTimeout(timeout.intValue()) // 连接空闲超时(毫秒) 默认10000
                .setIdleConnectionTimeout(timeout.intValue()) // 连接空闲超时(毫秒) 默认10000
                .setSubscriptionConnectionMinimumIdleSize(minIdle) // 发布和订阅连接的最小空闲连接数
                .setSubscriptionConnectionPoolSize(maxActive) // 发布和订阅连接池大小 默认50
                .setDnsMonitoringInterval(timeout) // DNS监测时间间隔(毫秒)，默认5000*//*
                .setAddress("redis://"+host+":"+port);
        //config.setThreads(Runtime.getRuntime().availableProcessors());// 默认 16
        log.info("Redisson初始化成功,{}", config.toString());
        return Redisson.create(config);
    }*/

    @Bean
    public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
        RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofSeconds(expired)); // 设置Redis缓存有效期
        return RedisCacheManager
                .builder(RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory))
                .cacheDefaults(redisCacheConfiguration).build();
    }

    @Bean
    public LettuceConnectionFactory lettuceConnectionFactory(GenericObjectPoolConfig<Object> genericObjectPoolConfig) {
        // 单机版配置
        RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration();
        redisStandaloneConfiguration.setDatabase(database);
        redisStandaloneConfiguration.setHostName(host);
        redisStandaloneConfiguration.setPort(port);
        redisStandaloneConfiguration.setPassword(RedisPassword.of(password));

        // 集群版配置
		/*RedisClusterConfiguration redisClusterConfiguration = new RedisClusterConfiguration();
		String[] serverArray = clusterNodes.split(",");
		Set<RedisNode> nodes = new HashSet<RedisNode>();
		for (String ipPort : serverArray) {
			String[] ipAndPort = ipPort.split(":");
			nodes.add(new RedisNode(ipAndPort[0].trim(), Integer.valueOf(ipAndPort[1])));
		}
		redisClusterConfiguration.setPassword(RedisPassword.of(password));
		redisClusterConfiguration.setClusterNodes(nodes);
		redisClusterConfiguration.setMaxRedirects(maxRedirects);*/

        LettuceClientConfiguration lettuceClientConfiguration = LettucePoolingClientConfiguration.builder()
                .commandTimeout(Duration.ofMillis(timeout))
                .poolConfig(genericObjectPoolConfig)
                .build();
        return new LettuceConnectionFactory(redisStandaloneConfiguration, lettuceClientConfiguration);
    }

    /**
     * GenericObjectPoolConfig 连接池配置
     *
     * @return
     */
    @Bean
    public GenericObjectPoolConfig<Object> genericObjectPoolConfig() {
        GenericObjectPoolConfig<Object> genericObjectPoolConfig = new GenericObjectPoolConfig<Object>();
        genericObjectPoolConfig.setMaxIdle(maxIdle);
        genericObjectPoolConfig.setMinIdle(minIdle);
        genericObjectPoolConfig.setMaxTotal(maxActive);
        genericObjectPoolConfig.setMaxWaitMillis(maxWait);
        return genericObjectPoolConfig;
    }

    /**
     * Redis 序列化策略
     * @param lettuceConnectionFactory
     * @return
     */
    @Bean
    public RedisTemplate<String, Object> redisTemplate(LettuceConnectionFactory lettuceConnectionFactory) {
        RedisTemplate<String,Object> redisTemplate=new RedisTemplate<>();
        redisTemplate.setConnectionFactory(lettuceConnectionFactory);
        StringRedisSerializer stringRedisSerializer=new StringRedisSerializer();
        JsonStringRedisSerializer jsonStringRedisSerializer=new JsonStringRedisSerializer();
        redisTemplate.setKeySerializer(stringRedisSerializer);
        redisTemplate.setHashKeySerializer(stringRedisSerializer);
        redisTemplate.setHashValueSerializer(jsonStringRedisSerializer);
        redisTemplate.setValueSerializer(jsonStringRedisSerializer);
        log.info("Redis初始化成功");
        return redisTemplate;
    }

    private class JsonStringRedisSerializer implements RedisSerializer<Object> {
        private final Charset charset;

        public JsonStringRedisSerializer() {
            this(StandardCharsets.UTF_8);
        }

        public JsonStringRedisSerializer(Charset charset) {
            Assert.notNull(charset, "Charset must not be null!");
            this.charset = charset;
        }

        @Override
        public String deserialize(byte[] bytes) {
            return (bytes == null ? null : new String(bytes, charset));
        }

        @Override
        public byte[] serialize(Object object) {
            if (object == null) {
                return new byte[0];
            }
            if(object instanceof String){
                return object.toString().getBytes(charset);
            }else {
                String string = JSONUtil.toJsonStr(object);
                return string.getBytes(charset);
            }
        }
    }
}
